package br.com.vm.service.impl;

import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import br.com.vm.dao.MapaDAO;
import br.com.vm.dijkstra.Dijkstra;
import br.com.vm.dijkstra.graph.Vertice;
import br.com.vm.dijkstra.util.DijkstraUtil;
import br.com.vm.model.Mapa;
import br.com.vm.model.Rota;
import br.com.vm.model.vo.MelhorRotaReq;
import br.com.vm.model.vo.MelhorRotaResp;
import br.com.vm.service.MapaService;

@Service
public class MapaServiceImpl extends SpringBeanAutowiringSupport implements
		MapaService {

	private static final long serialVersionUID = 2116138972850255993L;

	@Autowired
	private MapaDAO mapaDAO;

	@Override
	@Transactional
	public void salvarMapa(Mapa poMapa) throws Exception {

		if (poMapa == null || !StringUtils.hasText(poMapa.getNome())) {
			throw new Exception("Map can not be null and needs a name!");
		}

		poMapa.setNome(poMapa.getNome().toUpperCase());

		List<Mapa> lMapa = mapaDAO.listarPorNome(poMapa.getNome());

		if (!CollectionUtils.isEmpty(lMapa)) {
			throw new Exception(poMapa.getNome()
					+ " already exists in database!");
		}

		if (CollectionUtils.isEmpty(poMapa.getRotas())) {
			throw new Exception("At least one route is requires for a Map: "
					+ poMapa.getNome());
		}

		for (Rota rota : poMapa.getRotas()) {
			if (rota.getOrigem() == null || rota.getDestino() == null
					|| rota.getDistancia() == null) {
				throw new Exception(
						"Source, Target and Distance can not be null from a route in Map "
								+ poMapa.getNome());
			}
			rota.setMapa(poMapa);
		}
		mapaDAO.salvar(poMapa);

	}

	@Override
	@Transactional
	public MelhorRotaResp obterMelhorRota(MelhorRotaReq poMelhorRotaReq)
			throws Exception {

		if (poMelhorRotaReq == null
				|| !StringUtils.hasText(poMelhorRotaReq.getNomeDoMapa())) {
			throw new Exception(
					"Map name is required to get the shortest route!");
		}

		if (!StringUtils.hasText(poMelhorRotaReq.getPontoDeOrigem())) {
			throw new Exception("Source is required to get the shortest route!");
		}

		if (!StringUtils.hasText(poMelhorRotaReq.getPontoDeDestino())) {
			throw new Exception("Target is required to get the shortest route!");
		}

		if (null == poMelhorRotaReq.getAutonomiaDoCaminhao()) {
			throw new Exception(
					"Autonomy is required to get the shortest route!");
		}

		if (null == poMelhorRotaReq.getValorDoLitroDoCombustivel()) {
			throw new Exception(
					"Cost per liter is required to get the shortest route!");
		}

		poMelhorRotaReq.setNomeDoMapa(poMelhorRotaReq.getNomeDoMapa()
				.toUpperCase());

		List<Mapa> lMapa = mapaDAO.listarPorNome(poMelhorRotaReq
				.getNomeDoMapa());

		if (CollectionUtils.isEmpty(lMapa)) {
			throw new Exception(poMelhorRotaReq.getNomeDoMapa()
					+ " does not exists!");
		}

		Set<Vertice> verticesDoMapa = DijkstraUtil.prepararVerticesDoMapa(lMapa
				.get(0));

		Dijkstra dijkstraAlg = new Dijkstra();

		Vertice verticeOrigem = DijkstraUtil.obterVerticePeloNome(
				verticesDoMapa, poMelhorRotaReq.getPontoDeOrigem());
		Vertice verticeDestino = DijkstraUtil.obterVerticePeloNome(
				verticesDoMapa, poMelhorRotaReq.getPontoDeDestino());

		if (verticeOrigem == null) {
			throw new Exception("Source: " + poMelhorRotaReq.getPontoDeOrigem()
					+ " does not exists in routes map!");
		}

		if (verticeDestino == null) {
			throw new Exception("Target: "
					+ poMelhorRotaReq.getPontoDeDestino()
					+ " does not exists in routes map!");
		}

		dijkstraAlg.processarCaminhos(verticeOrigem);

		List<Vertice> caminhos = new Dijkstra()
				.obterMenorCaminho(verticeDestino);

		MelhorRotaResp melhorRotaResp = new MelhorRotaResp();

		String melhorCaminho[] = new String[caminhos.size()];

		for (int index = 0; index < caminhos.size(); index++) {
			melhorCaminho[index] = new String();
			melhorCaminho[index] = ((Vertice) caminhos.get(index)).getNomeNoh();
		}

		melhorRotaResp.setPontosDaMelhorRota(melhorCaminho);
		if (verticeDestino.distanciaMinima > 0
				&& Double.POSITIVE_INFINITY != verticeDestino.distanciaMinima) {
			melhorRotaResp
					.setCusto((verticeDestino.distanciaMinima / poMelhorRotaReq
							.getAutonomiaDoCaminhao())
							* poMelhorRotaReq.getValorDoLitroDoCombustivel());
		} else {
			throw new Exception("From " + verticeOrigem.getNomeNoh() + " to "
					+ verticeDestino.getNomeNoh()
					+ " there is no routes in map!");
		}

		return melhorRotaResp;
	}

	@Override
	@Transactional
	public List<Mapa> listarMapas() throws Exception {
		return (List<Mapa>) mapaDAO.listarTodos();
	}

	@Override
	@Transactional
	public void removerMapa(Integer piId) throws Exception {
		mapaDAO.remover(new Mapa(piId));
	}

	@Override
	@Transactional
	public List<Mapa> listarMapasPorNome(String psNome) throws Exception {
		return mapaDAO.listarPorNome(psNome);
	}

}
